﻿#region Includes
using System;
using System.IO;
using System.Drawing;
using System.Collections;
using System.Collections.Generic;

using OpenTK;
using Color4 = OpenTK.Graphics.Color4;
using OpenTK.Graphics.OpenGL;

using GingerGL.IO;
using GingerGL.Math;
using GingerGL.Graphics;
#endregion

namespace GingerGL.Graphics.Models.BSP
{
    //------------------------------------------------------------//
    /// <summary>
    /// BSP Map
    /// </summary>
    public class BSP : IRenderable
    {
        //--------------------------------------------------------//
        #region Variables
        /// <summary>
        /// Default Tesselation
        /// </summary>
        public const int DefaultTesselation = 8;
        #endregion
        //--------------------------------------------------------//
        #region Properties
        //--------------------------------------------------------//
        #region Configuration
        /// <summary>
        /// Gets/Sets TRUE is object is visible
        /// </summary>
        public bool Visible { get; set; }
        /// <summary>
        /// Gets/Sets TRUE to Hide Lightmaps
        /// </summary>
        public bool ShowLightMaps { get; set; }
        /// <summary>
        /// Pause Update to Frustum
        /// </summary>
        public bool PauseCalculations { get; set; }
        /// <summary>
        /// Gets the scene settings
        /// <para>This is a list of all objects to load into the map</para>
        /// </summary>
        public string EntityString { get; private set; }
        #endregion
        //--------------------------------------------------------//
        #region Geometry Buffers
        /// <summary>
        /// Gets Index Buffer for Meshs
        /// </summary>
        public IndexBuffer Indicies { get; private set; }
        /// <summary>
        /// Gets Vertex Buffer for Mesh Vertices
        /// </summary>
        public VertexBuffer Vertices { get; private set; }
        #endregion
        //--------------------------------------------------------//
        #region Table of Contents
        /// <summary>
        /// Gets the BSP Header Information which contain all directory infommation
        /// </summary>
        public BSPHeader Header { get; private set; }        
        /// <summary>
        /// Gets the Face Directory Information
        /// </summary>
        public FaceDirectoryEntry[] FaceDirectory { get; private set; }
        #endregion
        //--------------------------------------------------------//
        #region Faces
        /// <summary>
        /// Gets the Polygon Faces
        /// </summary>
        public PolygonFace[] PolygonFaces { get; private set; }
        /// <summary>
        /// Gets Mesh Faces
        /// </summary>
        public MeshFace[] MeshFaces { get; private set; }
        /// <summary>
        /// Gets Pathes
        /// </summary>
        public Patch[] Patches { get; private set; }
        /// <summary>
        /// Holds which faces need to be drawn
        /// </summary>
        public BitArray FacesToDraw { get; private set; }
        #endregion
        //--------------------------------------------------------//
        #region Textures
        /// <summary>
        /// Gets Texture Collection
        /// </summary>
        public Texture[] Textures { get; private set; }
        /// <summary>
        /// Gets Texture Collection
        /// </summary>
        public Texture[] LightMaps { get; private set; }
        /// <summary>
        /// Get White Texture
        /// </summary>
        public Texture WhiteTexture { get; private set; }
        #endregion
        //--------------------------------------------------------//
        #region Binary Space Partitioning
        /// <summary>
        /// Gets BSP Leaves
        /// </summary>
        public Leaf[] Leaves { get; private set; }
        /// <summary>
        /// Gets Leaf Faces
        /// </summary>
        public int[] LeafFaces { get; private set; }
        /// <summary>
        /// Gets Planes for BSP tree
        /// </summary>
        public Plane[] Planes { get; private set; }
        /// <summary>
        /// Gets nodes for bsp tree
        /// </summary>
        public BSPNode[] Nodes { get; private set; }
        /// <summary>
        /// Gets Visibility Data Header
        /// </summary>
        public BSPVisibilityData VisibilityHeader { get; private set; }
        /// <summary>
        /// Gets Visibility Data
        /// </summary>
        public BitArray VisibilityData { get; private set; }
        #endregion
        //--------------------------------------------------------//
        #endregion
        //--------------------------------------------------------//
        #region Constructors
        /// <summary>
        /// Initalizes a new instance of object
        /// </summary>
        public BSP()
        {
            this.Visible = true;
            this.PauseCalculations = false;
            this.ShowLightMaps = true;
        }
        #endregion
        //--------------------------------------------------------//
        #region Methods
        //--------------------------------------------------------//
        #region Loading Methods
        /// <summary>
        /// Load a BSP File
        /// </summary>
        static public BSP Load(string filename) { return Load((Stream)File.OpenRead(filename), filename, DefaultTesselation); }
        /// <summary>
        /// Load a BSP File
        /// </summary>
        static public BSP Load(string filename, int curveTesselation) { return Load((Stream)File.OpenRead(filename), filename, curveTesselation); }
        /// <summary>
        /// Loads a BSP File
        /// </summary>
        /// <param name="filename">Path to File</param>
        /// <param name="curveTesselation">Curve Tesselation</param>
        /// <returns>TRUE if successful</returns>
        static public BSP Load(Stream fs, string filename, int curveTesselation)
        {
            BSP ibsp = new BSP();
            ibsp.Header = Reader.ReadObject<BSPHeader>(fs);                         //load header
            if (Reader.FixString(ibsp.Header.Name) != "IBSP" || ibsp.Header.Version != 0x2E)
                throw new FileLoadException(String.Format("%s is not a version 0x2E .bsp map file", filename));
            if (!ibsp.LoadVertices(fs)) return null;                                //Load in vertices
            int numMeshIndices =                                                    //Calculate number of indices
                ibsp.Header.DirectoryEntries[(int)DirectoryEntryType.MeshIndices].Length / sizeof(int);
            Reader.StreamOffset(                                                    //move to indices data position
                fs, ibsp.Header.DirectoryEntries[(int)DirectoryEntryType.MeshIndices].Offset);
            ibsp.Indicies = new IndexBuffer();                                      //init index buffer
            ibsp.Indicies.Write(Reader.ReadObjects<Int32>(fs, numMeshIndices));     //load index
            ibsp.Indicies.Commit();                                                 //commit indices to video memory
            ibsp.Indicies.Clear();                                                  //dont need the data going forward
            if (!ibsp.LoadFaces(fs, curveTesselation)) return null;                 //Load in faces
            if (!ibsp.LoadTextures(fs)) return null;                                //Load textures
            if (!ibsp.LoadLightmaps(fs)) return null;                               //Load Lightmaps
            if (!ibsp.LoadBSPData(fs)) return null;                                 //Load BSP Data
                                                                                    //Go to entity string in file
            Reader.StreamOffset(fs, ibsp.Header.DirectoryEntries[(int)DirectoryEntryType.Entities].Offset);
            ibsp.EntityString = Reader.ReadString(fs, ibsp.Header.DirectoryEntries[(int)DirectoryEntryType.Entities].Length);
            fs.Close();                                                             //Output the entity string
            return ibsp;                                                            //return map
        }
        /// <summary>
        /// Load Face Data
        /// </summary>
        /// <param name="file">File Stream</param>
        /// <param name="curveTesselation">Curve Tesselation</param>
        /// <returns>TRUE if successful</returns>
        private bool LoadFaces(Stream file, int curveTesselation)
        {
            //calculate number of load faces
            int numTotalFaces = Header.DirectoryEntries[(int)DirectoryEntryType.Faces].Length / BSPFace.SizeInBytes;
            //go to faces in file
            Reader.StreamOffset(file, Header.DirectoryEntries[(int)DirectoryEntryType.Faces].Offset);
            //read in the faces
            BSPFace[] loadFaces = Reader.ReadObjects<BSPFace>(file, numTotalFaces);
            //Create space for face directory
            FaceDirectory = new FaceDirectoryEntry[numTotalFaces];
            FacesToDraw = new BitArray(numTotalFaces);
            //Calculate how many of each face type there is
            int numPolygonFaces = 0, numPatches = 0, numMeshFaces = 0;
            for (int i = 0; i < numTotalFaces; ++i)
            {
                if (loadFaces[i].Type == (int)FaceType.PolygonFace)  ++numPolygonFaces;
                if (loadFaces[i].Type == (int)FaceType.Patch)        ++numPatches;
                if (loadFaces[i].Type == (int)FaceType.MeshFace)     ++numMeshFaces;
            }
            //Create space for BSP_POLYGON_FACEs
            PolygonFaces = new PolygonFace[numPolygonFaces];
            int currentFace = 0;
            //convert loadFaces to polygonFaces
            for (int i = 0; i < numTotalFaces; ++i)
            {
                if (loadFaces[i].Type != (int)FaceType.PolygonFace)		//skip this loadFace if it is not a polygon face
                    continue;
                this.PolygonFaces[currentFace].FirstVertexIndex = loadFaces[i].FirstVertexIndex;
                this.PolygonFaces[currentFace].NumVertices = loadFaces[i].NumVertices;
                this.PolygonFaces[currentFace].TextureIndex = loadFaces[i].Texture;
                this.PolygonFaces[currentFace].LightmapIndex = loadFaces[i].LightmapIndex;

                //fill in this entry on the face directory
                this.FaceDirectory[i].FaceType = FaceType.PolygonFace;
                this.FaceDirectory[i].TypeFaceNumber = currentFace;
                ++currentFace;
            }

            //Create space for BSP_MESH_FACEs
            this.MeshFaces = new MeshFace[numMeshFaces];
            int currentMeshFace = 0;
            //convert loadFaces to faces
            for (int i = 0; i < numTotalFaces; ++i)
            {
                //skip this loadFace if it is not a mesh face
                if (loadFaces[i].Type != (int)FaceType.MeshFace)	continue;

                this.MeshFaces[currentMeshFace].FirstVertexIndex = loadFaces[i].FirstVertexIndex;
                this.MeshFaces[currentMeshFace].NumVertices = loadFaces[i].NumVertices;
                this.MeshFaces[currentMeshFace].TextureIndex = loadFaces[i].Texture;
                this.MeshFaces[currentMeshFace].LightmapIndex = loadFaces[i].LightmapIndex;
                this.MeshFaces[currentMeshFace].FirstMeshIndex = loadFaces[i].FirstMeshIndex;
                this.MeshFaces[currentMeshFace].NumMeshIndices = loadFaces[i].NumMeshIndices;

                //fill in this entry on the face directory
                FaceDirectory[i].FaceType = FaceType.MeshFace;
                FaceDirectory[i].TypeFaceNumber = currentMeshFace;
                ++currentMeshFace;
            }

            //Create space for BSP_PATCHes
            this.Patches = new Patch[numPatches];
            int currentPatch = 0;
            //convert loadFaces to patches
            for (int i = 0; i < numTotalFaces; ++i)
            {
                //skip this loadFace if it is not a patch
                if (loadFaces[i].Type != (int)FaceType.Patch) continue;

                this.Patches[currentPatch].TextureIndex = loadFaces[i].Texture;
                this.Patches[currentPatch].LightmapIndex = loadFaces[i].LightmapIndex;
                this.Patches[currentPatch].Width = loadFaces[i].PatchSize[0];
                this.Patches[currentPatch].Height = loadFaces[i].PatchSize[1];
                //fill in this entry on the face directory
                FaceDirectory[i].FaceType = FaceType.Patch;
                FaceDirectory[i].TypeFaceNumber = currentPatch;
                //Create space to hold quadratic patches
                int numPatchesWide = (this.Patches[currentPatch].Width - 1) / 2;
                int numPatchesHigh = (this.Patches[currentPatch].Height - 1) / 2;
                this.Patches[currentPatch].NumQuadraticPatches = numPatchesWide * numPatchesHigh;
                this.Patches[currentPatch].QuadraticPatches = new BiquadraticPatch[Patches[currentPatch].NumQuadraticPatches];

                //fill in the quadratic patches
                for (int y = 0; y < numPatchesHigh; ++y)
                {
                    for (int x = 0; x < numPatchesWide; ++x)
                    {
                        this.Patches[currentPatch].QuadraticPatches[y * numPatchesWide + x].ControlPoints = new Vertex[9];
                        for (int row = 0; row < 3; ++row)
                        {
                            for (int point = 0; point < 3; ++point)
                            {
                                this.Patches[currentPatch].QuadraticPatches[y * numPatchesWide + x].
                                    ControlPoints[row * 3 + point] = this.Vertices[loadFaces[i].FirstVertexIndex + (y * 2 * 
                                    this.Patches[currentPatch].Width + x * 2) + row * this.Patches[currentPatch].Width + point];
                            }
                        }

                        //tesselate the patch
                        this.Patches[currentPatch].QuadraticPatches[y * numPatchesWide + x].Tesselate(curveTesselation);
                    }
                }
                ++currentPatch;
            }
            return true;
        }
        /// <summary>
        /// Loads Vertices into the BSP Map
        /// </summary>
        /// <param name="file">Open File Stream</param>
        /// <returns>TRUE if successful</returns>
        private bool LoadVertices(Stream file)
        {
            //calculate number of vertices
            int numVertices = Header.DirectoryEntries[(int)DirectoryEntryType.Vertices].Length / BSPVertex.SizeInBytes;
            //go to vertices in file
            Reader.StreamOffset(file, Header.DirectoryEntries[(int)DirectoryEntryType.Vertices].Offset);
            //read in the vertices
            BSPVertex[] loadVertices = Reader.ReadObjects<BSPVertex>(file, numVertices);

            //Convert to BSP_VERTEXes
            this.Vertices = new VertexBuffer();

            for (int i = 0; i < numVertices; ++i)
            {
                Vertex v = new Vertex();
                //swap y and z and negate z
                v.Position = new Vector3(loadVertices[i].Position[0], loadVertices[i].Position[2], -loadVertices[i].Position[1]);
                //scale down
                v.Position /= 64;
                //Transfer texture coordinates (Invert t)
                v.Texture = new Vector2(loadVertices[i].DecalS, -loadVertices[i].DecalT);
                //Transfer lightmap coordinates
                v.LightMap = new Vector2(loadVertices[i].LightMapS, -loadVertices[i].LightMapT);
                this.Vertices.Write(v);
            }
            //commit the vertices to video memory
            this.Vertices.Commit();

            loadVertices = null;
            return true;
        }
        /// <summary>
        /// Loads Textures
        /// </summary>
        private bool LoadTextures(Stream file)
        {
            //Calculate number of textures
	        int numTextures=Header.DirectoryEntries[(int)DirectoryEntryType.Textures].Length/BSPTextureData.SizeInBytes;
	        //Load textures
            Reader.StreamOffset(file,Header.DirectoryEntries[(int)DirectoryEntryType.Textures].Offset);
            //load
            BSPTextureData[] loadTextures = Reader.ReadObjects<BSPTextureData>(file, numTextures);
	        //Create storage space for that many texture identifiers
	        this.Textures = new Texture[numTextures];
	        //Loop through and create textures
	        for(int i=0; i<numTextures; ++i)
	        {
                //add file extension to the name
                string jpgExtendedName = @"data\" + Reader.FixString(loadTextures[i].Name) + ".jpg";
                if (File.Exists(jpgExtendedName)) { Textures[i] = Texture.Instance.GetTexture(jpgExtendedName); }
                else { Textures[i] = null; }
	        }

            loadTextures = null;
	        return true;
        }
        /// <summary>
        /// Loads Lightmaps
        /// </summary>
        private bool LoadLightmaps(Stream file)
        {
            //Calculate number of lightmaps
            int numLightmaps = Header.DirectoryEntries[(int)DirectoryEntryType.Lightmaps].Length / BSPLightmap.SizeInBytes;
            //Load 
            Reader.StreamOffset(file,Header.DirectoryEntries[(int)DirectoryEntryType.Lightmaps].Offset);
            BSPLightmap[] loadLightmaps = Reader.ReadObjects<BSPLightmap>(file, numLightmaps);
            //Create storage space for that many texture identifiers
            this.LightMaps = new Texture[numLightmaps];
            //Change the gamma settings on the lightmaps (make them brighter)
            float gamma = 2.5f;
            for (int i = 0; i < numLightmaps; ++i)
            {
                int ndex = 0;

                Bitmap bmp = new Bitmap(128, 128);
                for (int j = 0; j < 128; ++j)
                {
                    for( int k = 0; k < 128; ++k)
                    {
                        byte r = loadLightmaps[i].LightmapData[ndex + 0];
                        byte g = loadLightmaps[i].LightmapData[ndex + 1];
                        byte b = loadLightmaps[i].LightmapData[ndex + 2];
                        bmp.SetPixel(k, j, Color.FromArgb(r, g, b));
                        ndex += 3;
                    }
                }
                ChangeGamma(ref bmp, gamma);
                //bmp.Save("lightmap.bmp");
                this.LightMaps[i] = Texture.Instance.FromImage(bmp);
            }
            Bitmap pixel = new Bitmap(1, 1);
            pixel.SetPixel(0, 0, Color.White);
            //Create white texture for if no lightmap specified
            this.WhiteTexture = Texture.Instance.FromImage(pixel);
            loadLightmaps = null;
            return true;
        }
        /// <summary>
        /// Loads Map Data
        /// </summary>
        private bool LoadBSPData(Stream file)
        {
            //----------------------------------------------------//
            #region Leaves
            //Load leaves
	        //Calculate number of leaves
	        int numLeaves=Header.DirectoryEntries[(int)DirectoryEntryType.Leaves].Length/BSPLeaf.SizeInBytes;
	        //Create space for this many BSP_LEAFs
	        this.Leaves=new Leaf[numLeaves];
	        //Load leaves
	        Reader.StreamOffset(file,Header.DirectoryEntries[(int)DirectoryEntryType.Leaves].Offset);
            BSPLeaf[] loadLeaves = Reader.ReadObjects<BSPLeaf>(file, numLeaves);
	        //Convert the load leaves to leaves
	        for(int i=0; i<numLeaves; ++i)
	        {
                this.Leaves[i].BoundingBoxVertices = new Vector3[8];
                this.Leaves[i].Cluster = loadLeaves[i].Cluster;
                this.Leaves[i].FirstLeafFace = loadLeaves[i].FirstLeafFace;
                this.Leaves[i].NumFaces = loadLeaves[i].NumFaces;

		        //Create the bounding box
                this.Leaves[i].BoundingBoxVertices[0] = new Vector3((float)loadLeaves[i].Mins[0], (float)loadLeaves[i].Mins[1], -(float)loadLeaves[i].Mins[2]);
                this.Leaves[i].BoundingBoxVertices[1] = new Vector3((float)loadLeaves[i].Mins[0], (float)loadLeaves[i].Mins[1], -(float)loadLeaves[i].Maxs[2]);
                this.Leaves[i].BoundingBoxVertices[2] = new Vector3((float)loadLeaves[i].Mins[0], (float)loadLeaves[i].Maxs[1], -(float)loadLeaves[i].Mins[2]);
                this.Leaves[i].BoundingBoxVertices[3] = new Vector3((float)loadLeaves[i].Mins[0], (float)loadLeaves[i].Maxs[1], -(float)loadLeaves[i].Maxs[2]);
                this.Leaves[i].BoundingBoxVertices[4] = new Vector3((float)loadLeaves[i].Maxs[0], (float)loadLeaves[i].Mins[1], -(float)loadLeaves[i].Mins[2]);
                this.Leaves[i].BoundingBoxVertices[5] = new Vector3((float)loadLeaves[i].Maxs[0], (float)loadLeaves[i].Mins[1], -(float)loadLeaves[i].Maxs[2]);
                this.Leaves[i].BoundingBoxVertices[6] = new Vector3((float)loadLeaves[i].Maxs[0], (float)loadLeaves[i].Maxs[1], -(float)loadLeaves[i].Mins[2]);
                this.Leaves[i].BoundingBoxVertices[7] = new Vector3((float)loadLeaves[i].Maxs[0], (float)loadLeaves[i].Maxs[1], -(float)loadLeaves[i].Maxs[2]);

		        for(int j=0; j<8; ++j)
                    Leaves[i].BoundingBoxVertices[j] /= 64;
            }
            #endregion
            //----------------------------------------------------//
            #region Leaf Faces
            //Load leaf faces array
	        int numLeafFaces=Header.DirectoryEntries[(int)DirectoryEntryType.LeafFaces].Length/sizeof(int);
	        //Create space for this many leaf faces
	        this.LeafFaces=new int[numLeafFaces];
	        //Load leaf faces
	        Reader.StreamOffset(file,Header.DirectoryEntries[(int)DirectoryEntryType.LeafFaces].Offset);
            for (int i = 0; i < LeafFaces.Length; i++) LeafFaces[i] = Reader.ReadObject<Int32>(file);
            #endregion
            //----------------------------------------------------//
            #region Planes
            //Load Planes
	        int numPlanes=Header.DirectoryEntries[(int)DirectoryEntryType.Planes].Length/Plane.SizeInBytes;
	        //Create space for this many planes
	        this.Planes=new Plane[numPlanes];
	        Reader.StreamOffset(file,Header.DirectoryEntries[(int)DirectoryEntryType.Planes].Offset);
            for (int i = 0; i < Planes.Length; i++) 
                Planes[i] = 
                    new Plane(
                        new Vector3(
                            Reader.ReadObject<float>(file), 
                            Reader.ReadObject<float>(file), 
                            Reader.ReadObject<float>(file)),
                            Reader.ReadObject<float>(file) / 64); //scale down
            #endregion
            //----------------------------------------------------//
            #region Nodes
            //Load nodes
	        int numNodes=Header.DirectoryEntries[(int)DirectoryEntryType.Nodes].Length/BSPNode.SizeInBytes;
	        //Create space for this many nodes
	        this.Nodes=new BSPNode[numNodes];
            Reader.StreamOffset(file,Header.DirectoryEntries[(int)DirectoryEntryType.Nodes].Offset);
            for (int i = 0; i < Nodes.Length; i++) { Nodes[i] = Reader.ReadObject<BSPNode>(file); }
            #endregion
            //----------------------------------------------------//
            #region Visibility Data
	        //Load visibility data
	        //load numClusters and bytesPerCluster
            //to do, you need to read in vis data after the structure
            Reader.StreamOffset(file,Header.DirectoryEntries[(int)DirectoryEntryType.VisData].Offset);
            this.VisibilityHeader = Reader.ReadObject<BSPVisibilityData>(file);
            this.VisibilityData = new BitArray(Reader.ReadObjects<byte>(file, this.VisibilityHeader.BytesPerCluster * this.VisibilityHeader.NumClusters));
            #endregion
            //----------------------------------------------------//
            loadLeaves = null;
            return true;
        }
        #endregion
        //--------------------------------------------------------//
        #region BSP Methods
        /// <summary>
        /// Gets true/false based on if a cluster is visible
        /// </summary>
        public bool IsClusterVisible(int cameraCluster, int testCluster)
        {
            if (cameraCluster >= 0 && testCluster >= 0)
                return this.VisibilityData[cameraCluster * VisibilityHeader.BytesPerCluster + testCluster / 8];
            return true;
        }
        /// <summary>
        /// BSP Functions
        /// calculate which leaf the camera lies in
        /// </summary>
        public int CalculateCameraLeaf(Vector3 cameraPosition)
        {
            int currentNode = 0;
            //loop until we find a negative index
            while (currentNode >= 0)
            {
                //if the camera is in front of the plane for this node, assign i to be the front node
                if (this.Planes[Nodes[currentNode].PlaneIndex].Intersects(cameraPosition) == PlaneIntersectionType.Front)
                    currentNode = this.Nodes[currentNode].Front;
                else
                    currentNode = this.Nodes[currentNode].Back;
            }
            //return leaf index
            return ~currentNode;
        }
        /// <summary>
        /// Finds Leafs based on position
        /// </summary>
        private int FindLeaf(Vector3 position)
        {
            int index = 0;
            float result = 0f;
            while (index >= 0)
            {
                BSPNode node = this.Nodes[index];
                Plane plane = this.Planes[node.PlaneIndex];
                result = (((plane.Normal.X * position.X) + (plane.Normal.Y * position.Y)) + (plane.Normal.Z * position.Z)) - plane.D;
                if (result >= 0f) { index = node.Front; }
                else { index = node.Back; }
            }
            return ~index;
        }
        /// <summary>
        /// Calculate which faces to draw given a position & frustum
        /// </summary>
        public void CalculateVisibleFaces()
        {
            //Clear the list of faces drawn
            this.FacesToDraw.SetAll(false);
            //calculate the camera leaf
            int cameraLeaf = CalculateCameraLeaf(Engine.Instance.CameraPosition);
            int cameraCluster = this.Leaves[cameraLeaf].Cluster;
            //loop through the leaves
            for (int i = 0; i < this.Leaves.Length; ++i)
            {
                //if the leaf is not in the PVS, continue
                if (!IsClusterVisible(cameraCluster, this.Leaves[i].Cluster)) 
                    continue;
                //if this leaf does not lie in the frustum, continue
                if (!Engine.Instance.Frustum.IsBoundingBoxInside(this.Leaves[i].BoundingBoxVertices))
                    continue;

                //loop through faces in this leaf and mark them to be drawn
                for (int j = 0; j < this.Leaves[i].NumFaces; ++j)
                    this.FacesToDraw.Set(this.LeafFaces[this.Leaves[i].FirstLeafFace + j], true);
            }
        }
        #endregion
        //--------------------------------------------------------//
        #region Draw Methods
        /// <summary>
        /// Render Mesh
        /// </summary>
        public void Draw()
        {
            //Engine.Instance.GameWindow.SwapBuffers();
            //if(!Engine.Instance.Paused)                                             //pause update of calcs
                //CalculateVisibleFaces();                                            //calculate what to draw
            if (!this.ShowLightMaps) GL.Color4(Color4.White);                       //Override lightmap?
            for (int i = 0; i < this.FaceDirectory.Length; ++i)                     //loop through faces
                //if (this.FacesToDraw[i])                                            //if this face is to be drawn, draw it
                    DrawFace(i);                                                    //draw this one!!!
        }
        /// <summary>
        /// Draws all face Items
        /// </summary>
        /// <param name="faceNumber">Face Number</param>
        public void DrawFace(int faceNumber)
        {
            switch (FaceDirectory[faceNumber].FaceType)
            {
                case FaceType.PolygonFace: DrawPolygonFace(FaceDirectory[faceNumber].TypeFaceNumber); break;
                case FaceType.MeshFace: DrawMeshFace(FaceDirectory[faceNumber].TypeFaceNumber); break;
                case FaceType.Patch: DrawPatch(FaceDirectory[faceNumber].TypeFaceNumber); break;
                default: return;
            }
            //Engine.Instance.GameWindow.SwapBuffers();
        }
        /// <summary>
        /// Draws Polygon Face
        /// </summary>
        /// <param name="polygonFaceNumber">Face Number</param>
        public void DrawPolygonFace(int polygonFaceNumber)
        {
            //skip this face if its texture was not loaded
            if (this.Textures[PolygonFaces[polygonFaceNumber].TextureIndex] == null) return;
            //----------------------------------------------------//
            #region Save State
            //Push current Enable Buffer state so we can restore it later
            GL.PushAttrib(AttribMask.AllAttribBits);
            //Push current Array Buffer state so we can restore it later
            GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit);
            #endregion
            //----------------------------------------------------//
            #region Setup Buffer
            //enable arrays
            GL.EnableClientState(ArrayCap.VertexArray);
            GL.EnableClientState(ArrayCap.TextureCoordArray);
            //set vertex buffer
            GL.BindBuffer(BufferTarget.ArrayBuffer, this.Vertices.VertexBufferId);
            #endregion
            //----------------------------------------------------//
            #region Vertex
            //set array pointers
            GL.VertexPointer(
                3, 
                VertexPointerType.Float, 
                Vertex.SizeInBytes, 
                Vertex.Offset(VertexOffset.Position));
            #endregion
            //----------------------------------------------------//
            #region Texture 1
            //Unit 1 - Lightmaps
            //setup blending
            GL.Disable(EnableCap.Blend);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Replace);
            //only bind a lightmap if one exists
            if (PolygonFaces[polygonFaceNumber].LightmapIndex >= 0)
                this.LightMaps[PolygonFaces[polygonFaceNumber].LightmapIndex].Apply();
            else
                this.WhiteTexture.Apply();
            //unit 1 - lightmap
            GL.TexCoordPointer(
                2,
                TexCoordPointerType.Float,
                Vertex.SizeInBytes,
                Vertex.Offset(VertexOffset.Texture1));
            //draw face
            GL.DrawArrays(
                BeginMode.TriangleFan,
                PolygonFaces[polygonFaceNumber].FirstVertexIndex,
                PolygonFaces[polygonFaceNumber].NumVertices);
            #endregion
            //----------------------------------------------------//
            #region Texture 0
            //set texture
            //GL.ClientActiveTexture(TextureUnit.Texture0);
            //unit 0 - decal texture
            this.Textures[PolygonFaces[polygonFaceNumber].TextureIndex].Apply();
            GL.Enable(EnableCap.Blend);
            GL.BlendFunc(BlendingFactorSrc.DstColor, BlendingFactorDest.SrcColor);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Replace);
            //unit 0
            GL.TexCoordPointer(
                2,
                TexCoordPointerType.Float,
                Vertex.SizeInBytes,
                Vertex.Offset(VertexOffset.Texture0));
            #endregion
            //----------------------------------------------------//
            #region Draw Array
            GL.DrawArrays(
                BeginMode.TriangleFan, 
                PolygonFaces[polygonFaceNumber].FirstVertexIndex, 
                PolygonFaces[polygonFaceNumber].NumVertices);
            #endregion
            //----------------------------------------------------//
            #region Restore State
            GL.Disable(EnableCap.Texture2D);
            GL.PopClientAttrib();
            GL.PopAttrib();
            #endregion
            //----------------------------------------------------//
        }
        /// <summary>
        /// Draws Mesh Face
        /// </summary>
        /// <param name="meshFaceNumber">Mesh Face Number</param>
        public void DrawMeshFace(int meshFaceNumber)
        {
            //skip this face if its texture was not loaded
            if (this.Textures[MeshFaces[meshFaceNumber].TextureIndex] == null) return;
            #region Save State
            //Push current Enable Buffer state so we can restore it later
            GL.PushAttrib(AttribMask.AllAttribBits);
            //Push current Array Buffer state so we can restore it later
            GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit);
            #endregion
            //----------------------------------------------------//
            #region Setup Buffer
            //enable arrays
            GL.EnableClientState(ArrayCap.VertexArray);
            GL.EnableClientState(ArrayCap.TextureCoordArray);
            //set vertex buffer
            GL.BindBuffer(BufferTarget.ArrayBuffer, this.Vertices.VertexBufferId);
            #endregion
            //----------------------------------------------------//
            #region Vertex
            //set array pointer
            GL.VertexPointer(
                3, 
                VertexPointerType.Float, 
                Vertex.SizeInBytes, 
                Vertex.Offset(VertexOffset.Position)
                + MeshFaces[meshFaceNumber].FirstVertexIndex * Vertex.SizeInBytes);
            #endregion
            //----------------------------------------------------//
            #region Elements
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, this.Indicies.IndexBufferId);
            #endregion
            //----------------------------------------------------//
            #region Texture 1
            //Unit 1 - Lightmaps
            //setup blending
            GL.Disable(EnableCap.Blend);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Replace);
            //only bind a lightmap if one exists
            if (MeshFaces[meshFaceNumber].LightmapIndex >= 0)
                this.LightMaps[MeshFaces[meshFaceNumber].LightmapIndex].Apply();
            else
                this.WhiteTexture.Apply();
            //unit 1 - lightmap
            GL.TexCoordPointer(
                2,
                TexCoordPointerType.Float,
                Vertex.SizeInBytes,
                Vertex.Offset(VertexOffset.Texture1)
                + MeshFaces[meshFaceNumber].FirstVertexIndex * Vertex.SizeInBytes);
            //draw face
            GL.DrawElements(
                BeginMode.Triangles,
                MeshFaces[meshFaceNumber].NumMeshIndices,
                DrawElementsType.UnsignedInt,
                MeshFaces[meshFaceNumber].FirstMeshIndex * sizeof(int));
            #endregion
            //----------------------------------------------------//
            #region Texture 0
            //set texture
            //GL.ClientActiveTexture(TextureUnit.Texture0);
            //unit 0 - decal texture
            this.Textures[MeshFaces[meshFaceNumber].TextureIndex].Apply();
            GL.Enable(EnableCap.Blend);
            GL.BlendFunc(BlendingFactorSrc.DstColor, BlendingFactorDest.SrcColor);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Replace);
            //unit 0
            GL.TexCoordPointer(
                2,
                TexCoordPointerType.Float,
                Vertex.SizeInBytes,
                Vertex.Offset(VertexOffset.Texture0)
                + MeshFaces[meshFaceNumber].FirstVertexIndex * Vertex.SizeInBytes);
            #endregion
            //----------------------------------------------------//
            #region Draw Elements
            GL.DrawElements(
                BeginMode.Triangles, 
                MeshFaces[meshFaceNumber].NumMeshIndices, 
                DrawElementsType.UnsignedInt, 
                MeshFaces[meshFaceNumber].FirstMeshIndex * sizeof(int));
            #endregion
            //----------------------------------------------------//
            #region Restore State
            GL.Disable(EnableCap.Texture2D);
            GL.PopClientAttrib();
            GL.PopAttrib();
            #endregion
            //----------------------------------------------------//
        }
        /// <summary>
        /// Draws Curved Surfaces
        /// </summary>
        /// <param name="patchNumber">Patch Number</param>
        public void DrawPatch(int patchNumber)
        {
            //skip this patch if its texture was not loaded
            if (this.Textures[Patches[patchNumber].TextureIndex] == null) return;
            #region Save State
            //Push current Enable Buffer state so we can restore it later
            GL.PushAttrib(AttribMask.AllAttribBits);
            //Push current Array Buffer state so we can restore it later
            GL.PushClientAttrib(ClientAttribMask.ClientVertexArrayBit);
            #endregion
            //----------------------------------------------------//
            for (int i = 0; i < this.Patches[patchNumber].NumQuadraticPatches; ++i)
                this.Patches[patchNumber].QuadraticPatches[i].Draw
                (
                    this.Textures[Patches[patchNumber].TextureIndex],
                    (this.Patches[patchNumber].LightmapIndex >= 0) ? this.LightMaps[this.Patches[patchNumber].LightmapIndex] : this.WhiteTexture
                );
            #region Restore State
            GL.Disable(EnableCap.Texture2D);
            GL.PopClientAttrib();
            GL.PopAttrib();
            #endregion
        }
        #endregion
        //--------------------------------------------------------//
        #region Static Methods
        /// <summary>
        /// Changes the gama of an image
        /// </summary>
        static public void ChangeGamma(ref Bitmap ImageBmp, float Factor)
        {
            for (int i = 0; i < ImageBmp.Width; i++)
            {
                for (int j = 0; j < ImageBmp.Height; j++)
                {
                    Color pixel = ImageBmp.GetPixel(i, j);
                    float r = pixel.R;
                    float g = pixel.G;
                    float b = pixel.B;

                    r *= Factor / 255.0f;
                    g *= Factor / 255.0f;
                    b *= Factor / 255.0f;

                    //find the value to scale back up
                    float scale = 1.0f;
                    float temp;
                    if (r > 1.0f && (temp = (1.0f / r)) < scale) scale = temp;
                    if (g > 1.0f && (temp = (1.0f / g)) < scale) scale = temp;
                    if (b > 1.0f && (temp = (1.0f / b)) < scale) scale = temp;

                    // scale up color values
                    scale *= 255.0f;
                    r *= scale;
                    g *= scale;
                    b *= scale;
                    ImageBmp.SetPixel(i, j, Color.FromArgb((int)r, (int)g, (int)b));
                }
            }
        }
        #endregion
        //--------------------------------------------------------//
        #region IRenderable Methods
        /// <summary>
        /// Disposes of object
        /// </summary>
        public void Dispose()
        {
            this.Indicies.Dispose();
            this.Vertices.Dispose();
            this.WhiteTexture.Dispose();
            foreach (Texture t in this.Textures) if (t != null) t.Dispose();
            foreach (Texture t in this.LightMaps) if (t != null) t.Dispose();
            foreach (Patch p in this.Patches) p.Dispose();
        }
        #endregion
        //--------------------------------------------------------//
        #region Collision Detection

        #endregion
        //--------------------------------------------------------//
        #endregion
        //--------------------------------------------------------//
    }
    //------------------------------------------------------------//
}
